package com.star.common.orm.repository.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.enums.SqlMethod;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.ReflectionKit;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import com.star.common.orm.annotation.BizKey;
import com.star.common.orm.entity.*;
import com.star.common.orm.repository.BaseRepository;
import com.star.common.orm.repository.RepositoryRegistry;
import com.star.common.orm.utils.Converter;
import lombok.Data;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.ibatis.binding.MapperMethod.ParamMap;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionHolder;
import org.mybatis.spring.SqlSessionUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.star.common.orm.entity.QueryParam.*;
/**
 * service实现类要继承的基类
 *
 * @param <MP> mapper接口
 * @param <M>  model的子类
 * @param <P>  数据库对应的实体类
 * @param <Q>  查询字段
 * @author zhouhengzhe
 */
@Slf4j
public abstract class BaseRepositoryImpl<MP extends BaseMapper<P>, M extends Model, P extends BasePO, Q extends QueryParam> implements BaseRepository<M, Q>, Serializable {
    @Resource
    @Getter
    private MP mapper;
    private final TableScheme tableScheme;
    private static final Map<Class, Class> ENTITY_MAPPING_MAP = new ConcurrentHashMap<>();
    private static final Pattern HUMP_PATTERN = Pattern.compile("[A-Z]");

    public BaseRepositoryImpl() {
        Class<M> modelClass = (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), this.getClass().getSuperclass(), 1);
        Class<P> poClass = (Class<P>) ReflectionKit.getSuperClassGenericType(this.getClass(), this.getClass().getSuperclass(), 2);
        Class<Q> paramClass = (Class<Q>) ReflectionKit.getSuperClassGenericType(this.getClass(), this.getClass().getSuperclass(), 3);
        this.tableScheme = initTableScheme(poClass);
        RepositoryRegistry.register(modelClass, this);
        RepositoryRegistry.register(paramClass, this);
        ENTITY_MAPPING_MAP.put(modelClass, poClass);
        ENTITY_MAPPING_MAP.put(poClass, modelClass);
    }

    public MP mapper() {
        return this.mapper;
    }

    @Override
    public boolean save(M model) {
        P po = convert(model);
        boolean result = SqlHelper.retBool(this.mapper.insert(po));
        if (result) {
            copyProperties(po, model);
        }
        return result;
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean save(List<? extends Model> models) {
        if (CollUtil.isEmpty(models)) {
            return false;
        }
        List<P> pos = convert(models);
        boolean result = insertBatch(pos, 100);
        if (result) {
            copyProperties(pos, models);
        }
        return result;
    }

    @Override
    public boolean update(M model) {
        P po = convert(model);
        boolean result = SqlHelper.retBool(this.mapper.updateById(po));
        if (result) {
            copyProperties(po, model);
        }
        return result;
    }

    @Override
    public boolean update(M model, Q param) {
        P po = convert(model);
        return SqlHelper.retBool(this.mapper.update(po, getBaseWrapper(param)));
    }

    @Override
    public boolean saveOrUpdate(M model) {
        return Objects.nonNull(model.getId()) ? this.update(model) : this.save(model);
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean update(List<? extends Model> models) {
        if (CollUtil.isEmpty(models)) {
            return false;
        }
        List<BasePO> pos = convert(models);
        boolean result = updateBatch(pos, 30);
        if (result) {
            copyProperties(pos, models);
        }
        return result;
    }

    @Override
    public boolean delete(Serializable id) {
        return id != null && SqlHelper.retBool(this.mapper.deleteById(id));
    }

    @Override
    public boolean delete(Q param) {
        return SqlHelper.retBool(this.mapper.delete(this.getBaseWrapper(param)));
    }

    @Override
    public boolean delete(List<? extends Serializable> ids) {
        if (CollUtil.isEmpty(ids)) {
            log.warn("batch function param is empty or null");
            return false;
        }
        if (ids.size() >= 100) {
            throw new IllegalArgumentException("当前批量删除的ID不能大于100");
        }
        return SqlHelper.retBool(this.mapper.deleteBatchIds(ids));
    }

    @Override
    public List<M> list(List<? extends Serializable> ids) {
        if (CollUtil.isEmpty(ids)) {
            throw new IllegalArgumentException("Error: ids must not be empty");
        }
        if (ids.size() >= 100) {
            throw new IllegalArgumentException("当前批量查询的ID不能大于100");
        }
        List<P> entityList = this.mapper.selectBatchIds(ids);
        return convert(entityList);
    }

    @Override
    public List<M> list(Q queryParam) {
        List<M> models = convert(this.mapper.selectList(this.getBaseWrapper(queryParam)));
        if (CollUtil.isNotEmpty(models)) {
            fill(queryParam, models);
        }
        return models;
    }

    @Override
    public M first(Q queryParam) {
        Page<M> page = this.page(1L, 1L, queryParam);
        if (page != null && page.getRecords() != null && page.getRecords().size() > 0) {
            return page.getRecords().get(0);
        } else {
            return null;
        }
    }

    @Override
    public M getOne(Q queryParam) {
        M model = convert(this.mapper.selectOne(this.getBaseWrapper(queryParam)));
        if (model != null) {
            fill(queryParam, Collections.singletonList(model));
        }
        return model;
    }

    @Override
    public M get(@NonNull Serializable id) {
        return convert(this.mapper.selectById(id));
    }

    @Override
    public long count(Q params) {
        return this.mapper.selectCount(this.getBaseWrapper(params));
    }

    @Override
    public boolean exist(Q param) {
        return SqlHelper.retBool(count(param));
    }

    @Override
    public Page<M> page(Q queryParam) {
        return this.page(queryParam.getPage(), queryParam.getLimit(), queryParam);
    }

    public Page<M> page(long page, long limit, Q param) {
        QueryWrapper<P> wrapper = this.getBaseWrapper(param);
        IPage<P> pageResult = this.mapper.selectPage(new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, limit), wrapper);
        Page<P> poPage = mapTo(pageResult);
        Page<M> modelPage = convert(poPage);
        if (modelPage.getRecords() != null && modelPage.getRecords().size() > 0) {
            fill(param, modelPage.getRecords());
        }
        return modelPage;
    }

    @Override
    public boolean deleteByKey(Serializable key) {
        return key != null && SqlHelper.retBool(this.mapper.delete(this.getKeyWrapper(key)));
    }

    @Override
    public boolean deleteByKeys(List<Serializable> keys) {
        if (keys.size() >= 100) {
            throw new IllegalArgumentException("批量删除的业务key不能大于100");
        }
        return CollUtil.isNotEmpty(keys) && SqlHelper.retBool(this.mapper.delete(this.getKeyWrapper(keys)));
    }

    @Override
    public boolean updateByKey(M model) {
        P po = convert(model);
        String key = this.getKeyValue(po);
        if (StrUtil.isBlank(key)) {
            throw new IllegalArgumentException("当前entity实体业务key字段为null，无法适用当前方法更新！");
        } else {
            return SqlHelper.retBool(this.mapper.update(po, this.getKeyWrapper(key)));
        }
    }

    @Override
    @Transactional(rollbackFor = {Exception.class})
    public boolean updateByKey(List<? extends Model> models) {
        if (CollUtil.isEmpty(models)) {
            log.warn("batch function param is empty or null");
            return false;
        }
        if (models.size() >= 100) {
            throw new IllegalArgumentException("批量更新的业务key不能大于100");
        }
        for (Model model : models) {
            this.updateByKey((M) model);
        }
        return true;
    }

    @Override
    public M getByKey(String key) {
        if (StrUtil.isBlank(key)) {
            throw new IllegalArgumentException("Error: key must not be blank");
        }
        return convert(this.mapper.selectOne(this.getKeyWrapper(key)));
    }

    @Override
    public List<M> listByKey(List<Serializable> keys) {
        if (CollUtil.isEmpty(keys)) {
            throw new IllegalArgumentException("Error: keys must not be empty");
        }
        if (keys.size() >= 100) {
            throw new IllegalArgumentException("批量查询的业务key不能大于100");
        }
        return convert(this.mapper.selectList(this.getKeyWrapper(keys)));
    }

    @Override
    public void fill(Q param, List<M> models) {
    }

    protected Map<String, Object> convert(Q queryParam) {
        return Converter.toMap(queryParam);
    }

    protected static <T, S> T convert(S source) {
        if (source == null) {
            return null;
        }
        Class<T> targetClass = ENTITY_MAPPING_MAP.get(source.getClass());
        return Converter.copy(source, targetClass);
    }

    protected static <T, S> List<T> convert(List<S> source) {
        if (CollUtil.isEmpty(source)) {
            return Collections.emptyList();
        }
        Class<T> targetClass = ENTITY_MAPPING_MAP.get(source.get(0).getClass());
        return Converter.copy(source, targetClass);
    }

    protected static <T, S> Page<T> convert(Page<S> poPage) {
        return poPage == null ? new Page<>(Collections.emptyList(), 0L) : new Page<>(convert(poPage.getRecords()), poPage.getTotal());
    }

    protected static <Q extends QueryParam, P extends BasePO> QueryWrapper<P> getDefaultWrapper(@Nullable Q queryParam) {
        return new QueryWrapper<>();
    }

    protected QueryWrapper<P> getKeyWrapper(Serializable key) {
        if (StrUtil.isBlank(this.tableScheme.bizKeyColumn)) {
            throw new IllegalArgumentException("当前entity实体没找到业务key字段，请在entity实体中使用@BizKey注解标记对应的字段！");
        } else {
            QueryWrapper<P> wrapper = getDefaultWrapper(null);
            wrapper.eq(this.tableScheme.bizKeyColumn, key);
            return wrapper;
        }
    }

    protected QueryWrapper<P> getKeyWrapper(List<Serializable> keys) {
        if (StrUtil.isBlank(this.tableScheme.bizKeyColumn)) {
            throw new IllegalArgumentException("当前entity实体没找到业务key字段，请在entity实体中使用@BizKey注解标记对应的字段！");
        } else {
            QueryWrapper<P> wrapper = getDefaultWrapper(null);
            wrapper.in(this.tableScheme.bizKeyColumn, keys);
            return wrapper;
        }
    }

    protected QueryWrapper<P> getBaseWrapper(Q queryParam) {
        QueryWrapper<P> wrapper = getDefaultWrapper(queryParam);
        setOrderBys(queryParam, wrapper);
        Map<String, Object> mapParams = this.convert(queryParam);
        if (mapParams != null) {
            mapParams.forEach((key, value) -> this.setCondition(wrapper, key, value));
            // 处理多个关键字or查询
            if (CollUtil.isNotEmpty(queryParam.getKeywords())) {
                wrapper.and(w -> queryParam.getKeywords().forEach((keyword, v) -> {
                    this.setCondition(w, keyword, v);
                    w.or();
                }));
            }
        }
        return wrapper;
    }

    private void setCondition(QueryWrapper<P> wrapper, String key, Object value) {
        if (this.isStringBlank(value) || key.equals(KEYWORDS_QUERY)) {
            return;
        }
        if (key.endsWith(START_QUERY) && this.isDateType(value)) {
            this.getColumnByField(this.getFieldName(key, START_QUERY), (p) -> wrapper.ge(p, value));
        } else if (key.endsWith(END_QUERY) && this.isDateType(value)) {
            this.getColumnByField(this.getFieldName(key, END_QUERY), (p) -> wrapper.le(p, value));
        } else if (key.endsWith(MIN_EQUALS_QUERY)) {
            this.getColumnByField(this.getFieldName(key, MIN_EQUALS_QUERY), (p) -> wrapper.ge(p, value));
        } else if (key.endsWith(MAX_EQUALS_QUERY)) {
            this.getColumnByField(this.getFieldName(key, MAX_EQUALS_QUERY), (p) -> wrapper.le(p, value));
        } else if (key.endsWith(MIN_QUERY)) {
            this.getColumnByField(this.getFieldName(key, MIN_QUERY), (p) -> wrapper.gt(p, value));
        } else if (key.endsWith(MAX_QUERY)) {
            this.getColumnByField(this.getFieldName(key, MAX_QUERY), (p) -> wrapper.lt(p, value));
        } else if (key.endsWith(NOT_IN_QUERY) && this.isCollectionType(value) && ((Collection<?>) value).size() != 0) {
            this.getColumnByField(this.getFieldName(key, NOT_IN_QUERY), (p) -> wrapper.notIn(p, (Collection<?>) value));
        } else if (key.endsWith(IN_QUERY) && this.isCollectionType(value) && ((Collection<?>) value).size() != 0) {
            this.getColumnByField(this.getFieldName(key, IN_QUERY), (p) -> wrapper.in(p, (Collection<?>) value));
        } else if (key.endsWith(LIKE_QUERY) && value instanceof CharSequence) {
            this.getColumnByField(this.getFieldName(key, LIKE_QUERY), (p) -> wrapper.like(p, value));
        } else if (key.endsWith(LIKE_LEFT_QUERY) && value instanceof CharSequence) {
            this.getColumnByField(this.getFieldName(key, LIKE_LEFT_QUERY), (p) -> wrapper.likeLeft(p, value));
        } else if (key.endsWith(LIKE_RIGHT_QUERY) && value instanceof CharSequence) {
            this.getColumnByField(this.getFieldName(key, LIKE_RIGHT_QUERY), (p) -> wrapper.likeRight(p, value));
        } else if (key.endsWith(NOT_QUERY)) {
            this.getColumnByField(this.getFieldName(key, NOT_QUERY), (p) -> wrapper.ne(p, value));
        } else if (key.endsWith(IS_NULL_QUERY)) {
            this.getColumnByField(this.getFieldName(key, IS_NULL_QUERY), wrapper::isNull);
        } else if (key.endsWith(NOT_NULL_QUERY)) {
            this.getColumnByField(this.getFieldName(key, NOT_NULL_QUERY), wrapper::isNotNull);
        } else {
            this.getColumnByField(key, (p) -> wrapper.eq(p, value));
        }
    }

    protected static SqlSessionFactory sqlSessionFactory(Class<? extends BasePO> poClass) {
        return GlobalConfigUtils.currentSessionFactory(poClass);
    }

    protected SqlSession sqlSession(Class<? extends BasePO> poClass) {
        return SqlSessionUtils.getSqlSession(sqlSessionFactory(poClass));
    }

    protected static SqlSession sqlSessionBatch(Class<? extends BasePO> poClass) {
        return SqlHelper.sqlSessionBatch(poClass);
    }

    protected static void closeSqlSession(Class<? extends BasePO> poClass, SqlSession sqlSession) {
        SqlSessionUtils.closeSqlSession(sqlSession, GlobalConfigUtils.currentSessionFactory(poClass));
    }

    protected static void clearSqlSessionCache(Class<? extends BasePO> poClass) {
        SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory(poClass));
        boolean transaction = TransactionSynchronizationManager.isSynchronizationActive();
        if (sqlSessionHolder != null) {
            SqlSession sqlSession = sqlSessionHolder.getSqlSession();
            sqlSession.commit(!transaction);
        }
    }

    protected static String sqlStatement(Class<? extends BasePO> poClass, SqlMethod sqlMethod) {
        return SqlHelper.table(poClass).getSqlStatement(sqlMethod.getMethod());
    }

    public static <P extends BasePO> boolean insertBatch(List<P> pos, int batchSize) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
        if (CollUtil.isEmpty(pos)) {
            log.warn("batch function param is empty or null");
            return false;
        } else {
            Class<? extends BasePO> poClass = pos.get(0).getClass();
            clearSqlSessionCache(poClass);
            SqlSession batchSqlSession = sqlSessionBatch(poClass);
            int i = 0;
            String sqlStatement = sqlStatement(poClass, SqlMethod.INSERT_ONE);

            try {
                for (Iterator<P> var6 = pos.iterator(); var6.hasNext(); ++i) {
                    BasePO anEntityList = var6.next();
                    batchSqlSession.insert(sqlStatement, anEntityList);
                    if (i >= 1 && i % batchSize == 0) {
                        batchSqlSession.flushStatements();
                    }
                }

                batchSqlSession.flushStatements();
                return true;
            } finally {
                closeSqlSession(poClass, batchSqlSession);
            }
        }
    }

    public static <P extends BasePO> boolean updateBatch(List<P> pos, int batchSize) {
        Assert.isFalse(batchSize < 1, "batchSize must not be less than one");
        if (CollUtil.isEmpty(pos)) {
            log.warn("batch function param is empty or null");
            return false;
        } else {
            Class<? extends BasePO> poClass = pos.get(0).getClass();
            clearSqlSessionCache(poClass);
            SqlSession batchSqlSession = sqlSessionBatch(poClass);
            int i = 0;
            String sqlStatement = sqlStatement(poClass, SqlMethod.UPDATE_BY_ID);

            try {
                for (Iterator<P> iterator = pos.iterator(); iterator.hasNext(); ++i) {
                    BasePO po = iterator.next();
                    ParamMap<BasePO> param = new ParamMap<>();
                    param.put("et", po);
                    batchSqlSession.update(sqlStatement, param);
                    if (i >= 1 && i % batchSize == 0) {
                        batchSqlSession.flushStatements();
                    }
                }

                batchSqlSession.flushStatements();
                return true;
            } finally {
                closeSqlSession(poClass, batchSqlSession);
            }
        }
    }

    public static <T> Page<T> mapTo(IPage<T> source) {
        return new Page<>(source.getRecords(), source.getTotal());
    }

    protected boolean isCollectionType(Object o) {
        return o instanceof Collection;
    }

    protected boolean isDateType(Object o) {
        return o instanceof Date || o instanceof LocalDateTime || o instanceof LocalDate || o instanceof LocalTime;
    }

    protected boolean isNumericType(Object o) {
        return o instanceof Integer || o instanceof Long || o instanceof Float || o instanceof Double || o instanceof BigDecimal;
    }

    private boolean isStringBlank(Object value) {
        return value == null || value instanceof CharSequence && StrUtil.isBlank((CharSequence) value);
    }

    private String getFieldName(String fieldName, String queryAction) {
        return replaceLast(fieldName, queryAction, "").toLowerCase();
    }

    protected void getColumnByField(String fieldName, Consumer<String> action) {
        if (this.tableScheme.getMapField2Column().containsKey(fieldName.toLowerCase())) {
            action.accept(this.tableScheme.getMapField2Column().get(fieldName.toLowerCase()));
        }

    }

    protected static void copyProperties(Object source, Object target) {
        BeanUtils.copyProperties(source, target);
    }

    protected static void copyProperties(Collection sourceList, Collection targetList) {
        Iterator itSource = sourceList.iterator();
        Iterator itTarget = targetList.iterator();

        while (itSource.hasNext()) {
            Object source = itSource.next();
            Object target = itTarget.next();
            copyProperties(source, target);
        }

    }

    protected String getKeyValue(BasePO po) {
        try {
            Object value = this.tableScheme.bizKeyField.get(po);
            return value != null ? value.toString() : null;
        } catch (IllegalAccessException | IllegalArgumentException var3) {
            log.error("获取当前实体对象的key值出现错误！", var3);
            throw new IllegalArgumentException("获取当前实体对象的key值出现错误!");
        }
    }

    protected void setKeyValue(BasePO po, String key) {
        try {
            this.tableScheme.bizKeyField.set(po, key);
        } catch (IllegalAccessException | IllegalArgumentException var4) {
            log.error("设置当前实体对象的key值出现错误！", var4);
            throw new IllegalArgumentException("设置当前实体对象的key值出现错误!");
        }
    }

    /**
     * 设置默认排序
     *
     * @param queryParam
     */
    protected static <Q extends QueryParam> void setDefaultSortParams(Q queryParam) {
        if (queryParam != null && CollUtil.isEmpty(queryParam.getSorts())) {
            queryParam.setSorts(Collections.singletonList(SortParam.desc(BasePO.CREATE_TIME)));
        }
    }

    protected <Q extends QueryParam> void setOrderBys(Q queryParam, QueryWrapper<P> wrapper) {
        setDefaultSortParams(queryParam);
        if (queryParam != null && CollUtil.isNotEmpty(queryParam.getSorts())) {
            for (SortParam sortParam : queryParam.getSorts()) {
                String key = sortParam.getSortField().replace("_", "").toLowerCase();
                if (!this.tableScheme.getMapField2Column().containsKey(key)) {
                    throw new IllegalArgumentException(String.format("[%s]字段不存在，无法进行排序操作！", sortParam.getSortField()));
                }
                String col = this.tableScheme.getMapField2Column().get(key);
                if ("DESC".equalsIgnoreCase(sortParam.getSortType())) {
                    wrapper.orderByDesc(col);
                } else {
                    wrapper.orderByAsc(col);
                }
            }
        }
    }

    protected boolean checkIsDataColumn(String fieldName, Class<P> tClass) {
        if (StrUtil.isBlank(fieldName)) {
            return false;
        } else {
            Field field = FieldUtils.getField(tClass, fieldName, true);
            return field != null;
        }
    }

    /**
     * 驼峰转下划线
     *
     * @param humpString
     * @return
     */
    protected static String humpToLine(String humpString) {
        Matcher matcher = HUMP_PATTERN.matcher(humpString);
        StringBuffer sb = new StringBuffer();

        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }

        matcher.appendTail(sb);
        return sb.toString();
    }

    protected static TableScheme initTableScheme(Class<? extends BasePO> poClass) {
        if (poClass == null) {
            return null;
        } else {
            TableScheme tableScheme = new TableScheme();
            TableName table = poClass.getAnnotation(TableName.class);
            if (table == null) {
                return null;
            } else {
                tableScheme.setTableName(table.value());
                List<Field> fields = FieldUtils.getAllFieldsList(poClass);
                fields = fields.stream().filter((p) -> !Modifier.isStatic(p.getModifiers())).collect(Collectors.toList());
                if (CollUtil.isEmpty(fields)) {
                    return tableScheme;
                } else {
                    tableScheme.mapField2Column = new HashMap<>(fields.size());
                    for (Field item : fields) {
                        String fieldName = item.getName();
                        String column = humpToLine(fieldName);

                        tableScheme.mapField2Column.put(fieldName.toLowerCase(), column);
                        if (item.getAnnotation(BizKey.class) != null) {
                            tableScheme.bizKeyField = item;
                            tableScheme.bizKeyField.setAccessible(true);
                            tableScheme.bizKeyColumn = column;
                        }
                    }
                    return tableScheme;
                }
            }
        }
    }

    public static String replaceLast(String raw, String match, String replace) {
        if (raw == null || raw.length() == 0 || null == replace) {
            //参数不合法，原样返回
            return raw;
        }
        StringBuilder sBuilder = new StringBuilder(raw);
        int lastIndexOf = sBuilder.lastIndexOf(match);
        if (-1 == lastIndexOf) {
            return raw;
        }

        return sBuilder.replace(lastIndexOf, lastIndexOf + match.length(), replace).toString();
    }

    @Data
    public static class TableScheme {
        private Field bizKeyField;
        private String bizKeyColumn;
        private String tableName;
        private Map<String, String> mapField2Column;
    }
}
